/*
 * Andrew J. Hanson/ Mitja Hmeljak
 * Indiana University, Computer Science Department
 * 3D GLUT RGB  Graphics template 
 * 

 on Linux (X11) you could type:
 cc  -o 3dtemplate.linux 3dtemplate.cpp  -lglut -lGLU -lGL  -lm

 on Mac OS X (Aqua) you could type:
 c++  -o ps4.macosx ps4.cpp -framework OpenGL -framework GLUT

 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* some C++ includes: */
#include <map>
#include <vector>

#if defined (__APPLE__) || defined (MACOSX)
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

GLdouble myMat[16];

/* some C++ settings: */
using namespace std;

/***************************************************************/
/******************** MY GLOBAL CONSTANTS **********************/
/***************************************************************/

/* ---------------------- */
/* Useful utility macros: */
/* ---------------------- */
#define MY_SIGN(A) ((A) < 0.0 ? -1.0 : ((A) > 0.0 ? 1.0 : 0.0))
#define MY_MIN(A,B) ((A) <= (B) ? (A) : (B))
#define MY_MAX(A,B) ((A) >= (B) ? (A) : (B))
#define MY_INBETWEEN(A,X,B) ((MY_MIN(A,B) <= (X)) && ((X) <= MY_MAX(A,B)))

/***************************************************************/
/******************** MY GLOBAL VARIABLES **********************/
/***************************************************************/

int gMouseButtons = 0;

int gMouseModifiers = 0;

int X=0, Y=0, dX=0, dY=0;

/* OpenGL camera coordinates: */
GLfloat XC = 0.0, YC = 0.0, ZC = -10.0;

GLint viewport[4];
GLdouble mvmatrix[16], projmatrix[16];
GLdouble wx, wy, wz;

//int fixed_point_x = 320, fixed_point_y = 341;
int fixed_point_x = 0, fixed_point_y = 0;

#define MID_TERM_SCALING 1
#define MID_TERM_PROPER_SCALING 2
#define BUG_CATCHER 3
#define TRANSFORM_TEAPOT 4

int mouse_down_x, mouse_down_y;
int transformation_mode = 0;
#define TRANSFORMATION_MODES 7
GLboolean drawLinesInBugCatcher= GL_FALSE;

int drawingMode= BUG_CATCHER;

GLboolean mouse_just_down= GL_FALSE;
int gMainWindow;
int gMainWindowHeight, gMainWindowWidth;

float mat[16];
GLboolean refresh_display= GL_TRUE;

float rand_x, rand_y;
GLboolean bug_found= GL_TRUE;

/***** NOT USED  *******************/

/* menu IDs for GLUT menu-handling functions */
#define GC_MENU_EXIT        -9999
#define GC_MENU_RESET        -200
#define GC_MENU_CREATE         10
/* constants for GLUT window defaults */
#define GC_WIND_X_SIZE     640
#define GC_WIND_Y_SIZE     480
#define GC_WIND_X_POS      100
#define GC_WIND_Y_POS      100

/***************************************************************/
/************************ DRAW STUFF ***************************/
/***************************************************************/

/* ------------------------------------------------------------------------ */
/* myDrawCoordSys() - draw coordinate system (as pPinP or not) */
/* ------------------------------------------------------------------------ */
void myDrawCoordSys(GLfloat pSize) {
	int ix, iy, iz;
	float redColor3[] = { 1.0, 0.0, 0.0 };
	float greenColor3[] = { 0.0, 1.0, 0.0 };
	float blueColor3[] = { 0.0, 0.0, 1.0 };
	GLfloat tenthSize = pSize / 10.0;
	GLfloat twentiethSize = pSize / 20.0;

	/* save GL state attributes */
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	/* disable lots of potentially distracting stuff,
	 to render the coord sys axes as plain as possible */
	glDisable(GL_COLOR_MATERIAL);
	glDisable(GL_LIGHTING);
	glDisable(GL_POINT_SMOOTH);
	glDisable(GL_LINE_SMOOTH);
	glLineWidth(1.0);

	/* draw X axis */
	glColor3fv(redColor3);
	glBegin(GL_LINES);
	glVertex3f( 0.0, 0.0, 0.0);
	glVertex3f(pSize, 0.0, 0.0);
	glEnd();
	for (ix=1; ix <=10; ix = ix + 1) {
		float f_ix = (float) ((pSize*(float)(ix) ) / 10.0);
		glBegin(GL_LINES);
		glVertex3f(f_ix, 0.0, 0.0);
		glVertex3f(f_ix, twentiethSize, 0.0);
		glEnd();
		glBegin(GL_LINES);
		glVertex3f(f_ix, 0.0, 0.0);
		glVertex3f(f_ix, 0.0, twentiethSize);
		glEnd();
	}
	/* draw Y axis */
	glColor3fv(greenColor3);
	glBegin(GL_LINES);
	glVertex3f(0.0, 0.0, 0.0);
	glVertex3f(0.0, pSize, 0.0);
	glEnd();
	for (iy=1; iy <=10; iy = iy + 1) {
		float f_iy = (float) ((pSize*(float)(iy) ) / 10.0);
		glBegin(GL_LINES);
		glVertex3f( 0.0, f_iy, 0.0);
		glVertex3f(twentiethSize, f_iy, 0.0);
		glEnd();
		glBegin(GL_LINES);
		glVertex3f( 0.0, f_iy, 0.0);
		glVertex3f( 0.0, f_iy, twentiethSize);
		glEnd();
	}
	/* draw Z axis */
	glColor3fv(blueColor3);
	glBegin(GL_LINES);
	glVertex3f(0.0, 0.0, 0.0);
	glVertex3f(0.0, 0.0, pSize);
	glEnd();
	for (iz=1; iz <=10; iz = iz + 1) {
		float f_iz = (float) ((pSize*(float)(iz) ) / 10.0);
		glBegin(GL_LINES);
		glVertex3f( 0.0, 0.0, f_iz);
		glVertex3f(twentiethSize, 0.0, f_iz);
		glEnd();
		glBegin(GL_LINES);
		glVertex3f( 0.0, 0.0, f_iz);
		glVertex3f( 0.0, twentiethSize, f_iz);
		glEnd();
	}

	/* restore GL state attributes */
	glPopAttrib();

} /* myDrawCoordSys() */

void debug(char *message){
	fprintf(stderr, message);
}

int min(int a, int b) {
	return a<b ? a : b;
}

void drawPolygon() {
	int start_x = 100, start_y = 100, index;
	glBegin(GL_LINES);
	for (index = 0; index < (gMainWindowWidth-180); index += 20) {
		glVertex2f(start_x + index, start_y);
		glVertex2f(start_x + index, gMainWindowHeight - 100);
		glVertex2f(start_x, start_y + index);
		glVertex2f(gMainWindowWidth - 100, start_y + index);
	}
	glEnd();

}

void scaleObject() {

	if (fixed_point_x > 1 && fixed_point_y > 1) {
		// get the object back to initial place
		glTranslatef(fixed_point_x, fixed_point_y, 0.0);

		glScalef((float)(X- fixed_point_x)/1000 + 1, (float)(Y - fixed_point_y)
				/1000 + 1, 1.0);

		// get the object to origin
		glTranslatef(-fixed_point_x, -fixed_point_y, 0.0);
	}
}

void transformObject() {

	if (gMouseButtons == 0x1) {
		glMatrixMode(GL_MODELVIEW);
		float mat[16];
		glGetFloatv(GL_MODELVIEW_MATRIX,mat);
		glLoadIdentity();
		if (refresh_display == GL_FALSE) {
			scaleObject();
			glMultMatrixf(mat);
		} else {
			refresh_display = GL_FALSE;
		}
	} else {
		glLoadIdentity();
	}
	drawPolygon();

	//	sprintf(stderr, "Fixed Point (%d, %d)", fixed_point_x, fixed_point_y);
	glPushMatrix();
	glLoadIdentity();
	glColor3f(0.8, 0.6, 0.2);
	glPointSize(5.0);

	// if we are in the proper scaling mode, change the point on the screen back to screen cordinates.
	double winX, winY, winZ;
	if (drawingMode == MID_TERM_PROPER_SCALING) {

		/* Get window coordinates */
		gluProject(fixed_point_x, fixed_point_y, 0.0, mvmatrix, projmatrix,
				viewport, &winX, &winY, &winZ);

	} else {
		winX = fixed_point_x;
		winY = fixed_point_y;
	}

	glBegin(GL_POINTS);
	glVertex2f(winX, winY);
	glEnd();
	glPopMatrix();
	glFlush();

}

void catchTheBug() {

	fprintf(stderr, "Catching Bugs\n");

	glLoadIdentity();
	float screen_center_x = (float)gMainWindowWidth/2.0;
	float screen_center_y = (float)gMainWindowHeight/2.0;

	float current_x = rand_x * gMainWindowWidth;
	float current_y = rand_y * gMainWindowHeight;
	bug_found = X < (current_x + 2) && X>(current_x - 2) && Y < (current_y + 2)
			&& Y>(current_y - 2);

	if (bug_found == GL_TRUE) {
		rand_x = drand48();
		rand_y = drand48();

		// flash the screen
		glColor3f(0.8, 0.3, 0.77);
		int j = 0;
		for (j = 0; j < 10; j++) {
			glRecti(drand48() * gMainWindowWidth,
					drand48() * gMainWindowHeight,
					drand48() * gMainWindowWidth, drand48() * gMainWindowHeight);
		}

		bug_found = GL_FALSE;
	}

	// draw the cross hair
	glColor3f(0.8, 0.2, 0.7);
	glLineWidth(1.0);
	glBegin(GL_LINES);
	glVertex2f(screen_center_x - 20, screen_center_y);
	glVertex2f(screen_center_x + 20, screen_center_y);
	glVertex2f(screen_center_x, screen_center_y - 20);
	glVertex2f(screen_center_x, screen_center_y + 20);
	glEnd();

	if (drawLinesInBugCatcher == GL_TRUE) {
		glColor3f(0.1, 0.6, 0.55);
		glBegin(GL_LINES);
		glVertex2f(screen_center_x, screen_center_y);
		glVertex2f(X, Y);
		glEnd();
	}

	glLoadIdentity();
	glTranslatef(screen_center_x, screen_center_y, 0.0);

	switch (transformation_mode) {
	case 0:
		glScalef(1, 1, 0);
		break;
	case 1:
		glScalef(-1, 1, 0);
		break;
	case 2:
		glScalef(1, -1, 0);
		break;
	case 3:
		glScalef(-1, -1, 0);
		break;
	case 4:
		glRotatef(90, 0.0, 0.0, 1.0);
		break;
	case 5:
		glRotatef(90, 0.0, 0.0, 1.0);
		glScalef(-1, 1, 0);
		break;
	case 6:
		glRotatef(90, 0.0, 0.0, 1.0);
		glScalef(1, -1, 0);
		break;
	case 7:
		glRotatef(90, 0.0, 0.0, 1.0);
		glScalef(-1, -1, 0);
		break;
	}

	//	glScalef((float)Y/X, (float)X/Y, 0);
	glTranslatef(-screen_center_x, -screen_center_y, 0.0);

	if (drawLinesInBugCatcher == GL_TRUE) {
		glColor3f(0.1, 0.6, 0.55);
		glBegin(GL_LINES);
		glVertex2f(screen_center_x, screen_center_y);
		glVertex2f(X, Y);
		glEnd();
	}

	glutSolidTetrahedron();

	glPointSize(10.0);
	glBegin(GL_POINTS);
	glColor3f(0.2, 0.2, 0.7);
	glVertex2f(rand_x * gMainWindowWidth, rand_y * gMainWindowHeight);
	glColor3f(0.7, 0.6, 0.99);
	glVertex2f(X, Y);
	glEnd();

	glLoadIdentity();
}

void setupLighting() {
	GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	GLfloat mat_shininess[] = { 50.0 };
	GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_SMOOTH);

	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);

}

void transformTeapot() {
	glColor3f(0.8, 0.4, 0.2);
	glLineWidth(7.0);

	setupLighting();

	// get the object back to initial place
	float scale_factor = 90.0 / (gMainWindowWidth);
	if (gMouseModifiers & GLUT_ACTIVE_SHIFT) {
		float ratio = (float)(X - fixed_point_x)/gMainWindowWidth;
		if (gMouseButtons == 0x1) {
			glTranslated(ratio, 0.0, 0.0);
		} else if (gMouseButtons == 0x2) {
			glTranslated(0.0, ratio, 0.0);
		} else if (gMouseButtons == 0x4) {
			glTranslated(0.0, 0.0, ratio);
		}
	} else if (gMouseButtons != 0x0) {
		glTranslated(0, 0.5, 0.0);
		if (gMouseButtons == 0x1) {
			glRotated((X - fixed_point_x) * scale_factor, 1.0, 0.0, 0.0);
		} else if (gMouseButtons == 0x4) {
			glRotated((X - fixed_point_x) * scale_factor, 0.0, 1.0, 0.0);
		} else if (gMouseButtons == 0x2) {
			glRotated((X - fixed_point_x) * scale_factor, 0.0, 0.0, 1.0);
		}
		glTranslated(0, -0.5, 0.0);
	}
	glutWireTeapot(1.0);
} /* transformTeapot() */

/***************************************************************/
/************************ DRAW STUFF ***************************/
/***************************************************************/

void drawObjs() {
	glColor3f(0.9, 0.8, 0.4);

	switch (drawingMode) {
	case MID_TERM_SCALING:
		transformObject();
		break;
	case MID_TERM_PROPER_SCALING:
		debug("In to Mid term proper scaling\n");
		transformObject();
		break;
	case BUG_CATCHER:
		catchTheBug();
		break;
	case TRANSFORM_TEAPOT:
		transformTeapot();
		break;

	}

}
/* -------------------------------------------------------------- */
/* -------------------------------------------------------------- */

/* -------------------------------------------------------------- */
/* GLUT callback to redisplay window content:
 all OpenGL drawing has to happen here and only here */
/* -------------------------------------------------------------- */
void display(void) {

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	/* just a little help to see where you are at first */
	//myDrawCoordSys(1.0); /* remove this call when you're done! */

	drawObjs();

	glutSwapBuffers();
}

/***************************************************************/
/********************** MOUSE/KB CALLBACKS *********************/
/***************************************************************/

/* -------------------------------------------------------------- */
/* Mouse Button  UP/DOWN  or DOWN/UP transition only */
/* -------------------------------------------------------------- */
void mouseButtons(int button, int state, int x, int y) {
	X = x;
	Y = gMainWindowHeight - y - 1; /* Invert to right-handed system */

	/*  This is by far the most elegant:
	 button = (1 << button);
	 but the guardians of correctness have pointed out that
	 this COULD be implementation dependent!  Thus we must
	 replace one line of elegant code by  11 lines of "correct" code! */

	switch (button) {
	case GLUT_LEFT_BUTTON:
		button = 0x1; /* our Left bit code */
		break;
	case GLUT_MIDDLE_BUTTON:
		button = 0x2; /* our Middle bit code */
		break;
		// case GLUT_RIGHT_BUTTON:
	}

	gMouseModifiers = glutGetModifiers();
	/* bitmask with GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_ALT */

	if (state == GLUT_DOWN) {
		gMouseButtons |= button;

		mouse_just_down = GL_TRUE;

		mouse_down_x = X;
		mouse_down_y = Y;

		if (drawingMode == MID_TERM_PROPER_SCALING) {
			glGetIntegerv(GL_VIEWPORT,viewport);
			glGetDoublev(GL_MODELVIEW_MATRIX,mvmatrix);
			glGetDoublev(GL_PROJECTION_MATRIX,projmatrix);
			/* realY = viewport[3] - (GLint) Y - 1; */
			gluUnProject((GLdouble) X, (GLdouble) Y, 0.0, mvmatrix, projmatrix,
					viewport, &wx, &wy, &wz);
			fixed_point_x = wx;
			fixed_point_y = wy;
		} else {
			fixed_point_x = X;
			fixed_point_y = Y;
		}

	} else
		gMouseButtons &= (~(button));

	printf("button  %x ~button %x   gMouseButtons %d \n", button, (~(button)),
			gMouseButtons);
	fprintf(stderr, "[%d, %d]", X, Y);

	glutPostRedisplay();
} /* mouseButtons() */

/* -------------------------------------------------------------- */
/* Mouse motion WHILE BUTTON DOWN  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
/* -------------------------------------------------------------- */
void motion(int x, int y) {

	X = x;
	Y = gMainWindowHeight - y - 1; /* Invert to right-handed system */

	switch (gMouseButtons) {
	case 0x1: /* Left Mouse Button   */
		break;
	case 0x2: /* Middle Mouse Button */
		break;
	case 0x4: /* Right Mouse Button  */
		break;
	}
	glutPostRedisplay();

} /* motion() */

/* -------------------------------------------------------------- */
/* Mouse motion WHILE ALL BUTTONS UP  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
/* -------------------------------------------------------------- */
void passive(int x, int y) {
	X = x;
	Y = gMainWindowHeight - y - 1;

	glutPostRedisplay();
} /* passive() */

/* Handle Keyboard and Help */
void initHelp() {
	/* Insert fprintf's for all controls you add */
	fprintf(stderr,"Press 'h' for help. \nPress ESC or 'q' to Exit.\n");
}

/* -------------------------------------------------------------- */
/* callback to handle keyboard */
/* -------------------------------------------------------------- */
void keyboard(unsigned char key, int x, int y) {
	/*  Option: To force all lower case */
	/* if (key >= 'A' && key <= 'Z') key = (key-'A') + 'a'; */

	//	glMatrixMode(GL_PROJECTION);
	//	glLoadIdentity();
	//	gluOrtho2D(0.0, (double)main_width-1, 0.0, (double)main_height-1);
	//	glMatrixMode(GL_MODELVIEW);

	switch (key) {
	case 'h':
		fprintf(stderr,"Help: goes here.\n");
		break;
	case '1':
		drawingMode = MID_TERM_SCALING;
		gluOrtho2D(0.0, (double)gMainWindowWidth-1, 0.0,
				(double)gMainWindowHeight-1);
		break;
	case '2':
		drawingMode = MID_TERM_PROPER_SCALING;
		gluOrtho2D(0.0, (double)gMainWindowWidth-1, 0.0,
				(double)gMainWindowHeight-1);
		break;
	case '3':
		drawingMode = BUG_CATCHER;
		gluOrtho2D(0.0, (double)gMainWindowWidth-1, 0.0,
				(double)gMainWindowHeight-1);
		break;
	case '4':
		drawingMode = TRANSFORM_TEAPOT;
		glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 25.0);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		//		XC = 0, YC = 0, ZC = -10;
		//		glTranslatef(XC, YC, ZC);
		break;
	case ' ':
		transformation_mode = (transformation_mode + 1) % TRANSFORMATION_MODES;
		break;
	case 'a':
		drawLinesInBugCatcher = drawLinesInBugCatcher == GL_FALSE ? GL_TRUE
				: GL_FALSE;
		break;
	case 'r':
		refresh_display = GL_TRUE;
		break;
	case 'q':
	case 27: /* 27 -> ESC */
		fprintf(stderr,"Normal Exit.\n");
		exit(EXIT_SUCCESS);
		break;
	default:
		fprintf(stderr,"Unhandled key: %c [%d] \n",key,key);
	}

	XC = 0, YC = 0, ZC = -10;
	glTranslatef(XC, YC, ZC);
	glutPostRedisplay();
}

/***************************************************************/
/************************** INIT GL ****************************/
/***************************************************************/

/* -------------------------------------------------------------- */
/* -------------------------------------------------------------- */
void initGL() {
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glEnable(GL_DEPTH_TEST);
} /* initGL() */

/***************************************************************/
/************************ GLUT STUFF ***************************/
/***************************************************************/

/* -------------------------------------------------------------- */
/* this gets called at least once by GLUT at window creation */
/* so it's a good place to initialize OpenGL projection */
/* -------------------------------------------------------------- */
void reshape(int w, int h) {
	w = MY_MAX(w,200);
	h = MY_MAX(h,200);
	gMainWindowHeight = h;
	gMainWindowWidth = w;
	glutReshapeWindow(w, h);

	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	/* 2D Projection matrices  */
	//  gluOrtho2D(0.0,(double)w-1,0.0,(double)h-1); 

	//  gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
	gluOrtho2D(0.0, (double)gMainWindowWidth-1, 0.0, (double)gMainWindowHeight
			-1);

	/*  3D projection in abstract floating point coordinate frame */
	/* glFrustum() describes a matrix
	 that  produces  a  perspective projection */
	//glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 25.0);

	/* Viewing location of camera */
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(XC, YC, ZC);
} /* reshape() */

/* -------------------------------------------------------------- */
/* -------------------------------------------------------------- */
void visibility(int state) {
	/* May also want to check PARTIAL DAMAGE that
	 does not result in a call to the visibility callback:
	 if (glutLayerGet(GLUT_NORMAL_DAMAGED)) {
	 redraw = TRUE; }
	 */
	/*  fprintf(stderr,"Comment this out: visibility: %s\n",
	 (state==GLUT_VISIBLE)?"visible":"invisible"); */
	if (state == GLUT_VISIBLE) {
		glutPostRedisplay();
	}
}

/*************  THIS IS NOT USED***********************************/
/* -------------------------------------------------------------- */
/* handleGlutMenus() - callback for GLUT menus */
/* -------------------------------------------------------------- */
void handleGlutMenus(int value) {
	switch (value) {
	case GC_MENU_RESET:
		/* DO NOT CLEAR HERE: Post a message to "display"! */
		//            myInitMyData();
		//            glMatrixMode(GL_MODELVIEW);
		//            glLoadIdentity();
		break;
	case GC_MENU_CREATE:
		//            gMyMode = GC_MENU_CREATE;
		//            dX=dY=rEta=0;
		//            sx=sy=1.0;
		//            glMatrixMode(GL_MODELVIEW);
		//            glLoadIdentity();
		break;
	case GC_MENU_EXIT:
		exit(0);
		break;
	default:
		fprintf(stderr,"unknown menu entry");
	} /* switch () */
	glutPostRedisplay(); /* make sure we Display according to changes */
} /* handleGlutMenus() */

/*************  THIS IS NOT USED***********************************/
/* -------------------------------------------------------------- */
/* initGlutMenus() - initialize GLUT Menus */
/* -------------------------------------------------------------- */
void initGlutMenus(void) {
	/* create a GLUT menu to appear when the right mouse button is clicked */
	glutCreateMenu(handleGlutMenus); /* the menu callback function */

	glutAddMenuEntry("Reset Everything", GC_MENU_RESET);
	glutAddMenuEntry(" ", -1);
	glutAddMenuEntry("Create Something", GC_MENU_CREATE);
	glutAddMenuEntry(" ", -2);
	glutAddMenuEntry("Exit", GC_MENU_EXIT);

	glutAttachMenu(GLUT_RIGHT_BUTTON); /* attach it to the right button. */
} /* initGlutMenus() */

/***************************************************************/
/**************************** MAIN *****************************/
/***************************************************************/

int main(int argc, char **argv) {

	/* GLUT Initialization */
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowPosition( GC_WIND_X_POS, GC_WIND_Y_POS);
	glutInitWindowSize( GC_WIND_X_SIZE, GC_WIND_Y_SIZE);

	/* Create a new window and give a title to it */
	gMainWindow = glutCreateWindow(":: PS4 : Eran Chinthaka ::");

	/* Initialize OpenGL stuff */
	initGL();

	/* Post Event Handlers */
	glutReshapeFunc(reshape);
	glutVisibilityFunc(visibility);
	glutDisplayFunc(display);
	glutMouseFunc(mouseButtons);
	glutMotionFunc(motion);
	glutPassiveMotionFunc(passive);

	glutKeyboardFunc(keyboard);

	glutIdleFunc(NULL);

	/* initialize GLUT menu handler only here, so it can take over access
	 to GLUT_RIGHT_BUTTON, regardles to what other handlers did to it */
	// initGlutMenus();

	initHelp();

	glutMainLoop();

	return (EXIT_SUCCESS);
}
